//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2003-2006
// Holux is registered trademark of Holux Technology Inc.
// ALAN and ALAN Map 500 are registered trademarks of ALAN Group.
//
// Holux CRD map parser.
//

# include "StdAfx.h"
# include "HoluxCrd.h"
# include "HoluxCrdLoader.h"
# include "Map.h"
# include "Types.h"
# include "HoluxTypes.h"
# include "HoluxRound.h"
# include "Load.h"
# include "Log.h"
# include "Status.h"
# include "ParseHelpers.h"
# include "Wrappers.h"
# include "Globals.h"

class CHoluxCrdLoader : public CCrdLoader {};
class CAlanCrdLoader  : public CCrdLoader {};

DECLARE_MAP_LOADER (CHoluxCrdLoader, "holux-crd", "crd", "Holux MapShow map (*.crd)");
DECLARE_MAP_LOADER (CAlanCrdLoader,  "alan-crd",  "crd", "ALAN Map 500 map (*.crd)");

bool CCrdLoader::IsValid (const char * _pData, size_t _cFileSize) const {
	if (_cFileSize < sizeof (CCrdHdr))
		return false;

	const CCrdHdr * const pHdr = reinterpret_cast<const CCrdHdr *> (_pData);

	const char * const pMapHdr_ = _pData + pHdr->dwIndexOffset + pHdr->dwIndexSize*sizeof (CCrdIndexItem) + c_cCrdTypes*sizeof (DWORD);
	if (pMapHdr_ - _pData + sizeof (CCrdMapHdr) > _cFileSize)
		return false;
	const CCrdMapHdr * const pMapHdr = reinterpret_cast<const CCrdMapHdr *> (pMapHdr_);

	if (
		pHdr->btAdmLevel <= 0x2 &&
		pHdr->btReserved [0] == 0 &&
		pHdr->btReserved [1] == 0 &&
		pHdr->btReserved [2] == 0 &&
		pHdr->dwAdmDataOffset    >= sizeof (CCrdHdr) &&
		pHdr->dwSubadmDataOffset >= sizeof (CCrdHdr) &&
//		pHdr->wCountry <= 255 &&
		pHdr->wAdmNum <= pHdr->wSubadmNum &&
		pHdr->btLanguage <= 2
	)
		return true;

	return false;
}

rgn_t & CCrdLoader::AddPoint (
	size_t _cLevel, USHORT _usType, RGN_KIND _Kind,
	float _x, float _y,
	string_t & _strLabel, city_t * _pCity, string_t & _strPhone,
	size_t _cBeginPos, size_t _cSize
) {
	rgn_t & rgn = m_pMap->CreateRgn (_Kind);

	// Initialize the rgn.
	rgn.cBeginPos = _cBeginPos;
	rgn.cEndPos = _cBeginPos + _cSize;
	rgn.cFileLine = -1;
	rgn.pFileRef = & m_file_refs.back ();

	rgn.SetType (m_pMap->pTypeSet, _usType, _Kind);

	if (! _strLabel.empty ())
		rgn.strLabel.swap (_strLabel);

	if (_pCity)
		rgn.pExtra->pCity = _pCity;

	if (! _strPhone.empty ())
		rgn.pExtra->strPhone.swap (_strPhone);

	rgn.origins.resize (1);
	rgn_origin_t & origin = rgn.origins [0];
	origin.cFileOffset = _cBeginPos;
	origin.cLevel = _cLevel;
	origin.point.x = _x;
	origin.point.y = _y;

	return rgn;
}

rgn_t & CCrdLoader::AddPoly (
	size_t _cLevel, USHORT _usType, RGN_KIND _Kind,
	points_t & _points,
	string_t & _strLabel, city_t * _pCity,
	size_t _cBeginPos, size_t _cSize
) {
	rgn_t & rgn = m_pMap->CreateRgn (_Kind);

	// Initialize the rgn.
	rgn.cBeginPos = _cBeginPos;
	rgn.cEndPos = _cBeginPos + _cSize;
	rgn.cFileLine = -1;
	rgn.pFileRef = & m_file_refs.back ();

	rgn.SetType (m_pMap->pTypeSet, _usType, _Kind);

	if (! _strLabel.empty ())
		rgn.strLabel.swap (_strLabel);

	if (_pCity)
		rgn.pExtra->pCity = _pCity;

	rgn.elements.resize (1);
	rgn_element_t & element = rgn.elements [0];
	element.cFileOffset = _cBeginPos;
	element.cLevel = _cLevel;
	element.points.swap (_points);
	element.bHole = false;

	return rgn;
}

void CCrdLoader::ReadString (string_t & _str, const CCrdStringRef & _Ref) {
	if (_Ref.dwOffset < m_cFileSize) {
		const char * const strName = m_pData + _Ref.dwOffset;
		_str.assign (strName, _Ref.dwLen);
	} else {
		char strName [32];
		::sprintf (strName, "damaged-%d", _Ref.dwOffset);
		_str = strName;
		ReportWarning (reinterpret_cast<const char *> (& _Ref) - m_pData, "String reference is out of file size.");
	}
}

LoadStatus_t CCrdLoader::Load (const char * _pData, size_t _cFileSize, const char * _strFileName, map_t * _pMap) {
	m_pMap = _pMap;

	m_pData = _pData;
	m_cFileSize = _cFileSize;
	const char * pEnd  = _pData + _cFileSize;

	m_file_refs.clear ();
	m_file_refs.push_back (file_ref_t ());
	m_file_refs.back ().strFileName = _strFileName;
	m_file_refs.back ().bAttachment = false;

	SetProgressStatus (0);

	//
	// Parse the header.
	//
	const CCrdHdr * const pHdr = reinterpret_cast<const CCrdHdr *> (_pData);
	m_pMap->cHeaderLen = sizeof (CCrdHdr);
	m_pMap->bNoHeader = false;
	m_pMap->pTypeSet = & g_tsHolux;
	m_pMap->Header.ElevationUnits = unMeters;

	if ((pHdr->dwSubadmDataOffset - pHdr->dwAdmDataOffset) != pHdr->wAdmNum*sizeof (CCrdAdm))
		ReportWarning ("Probably data in the file header are damaged.");

	// Country.
	char strCountry [20];
	::sprintf (strCountry, "%d", pHdr->wCountry);
	m_pMap->Countries.push_back (country_t ());
	country_t & country = m_pMap->Countries.back ();
	country.strName = strCountry;
	if (pHdr->wCountry > 255)
		ReportWarning ("Unexpected country code in header.");

	// Language.
	switch (pHdr->btLanguage) {
		case clAscii7:
			m_pMap->Header.wCodePage = 0;
			m_pMap->Header.CodeSchema = bpsAscii7;
			break;

		case clSBCS:
			m_pMap->Header.wCodePage = 1252; // Latin-I
			m_pMap->Header.CodeSchema = bpsSBCS;
			break;

		case clMBCSTaiwan:
			m_pMap->Header.wCodePage = 950;  // Traditional Chineese
			m_pMap->Header.CodeSchema = bpsMBCS;
			break;

		case clMBCSKorean:
			m_pMap->Header.wCodePage = 949;
			m_pMap->Header.CodeSchema = bpsMBCS;
			break;

		default:
			assert (false);
			break;
	}

	//
	// Parse administrations.
	//
	std::map<DWORD, region_t *> mapRegions;
	typedef std::map<DWORD, city_t *> mapCities_t;
	mapCities_t mapCities;
	size_t cCity = 0;

	const CCrdAdm * const pAdms = reinterpret_cast<const CCrdAdm *> (_pData + pHdr->dwAdmDataOffset);
	for (size_t cAdm = 0; cAdm < pHdr->wAdmNum; ++ cAdm) {
		m_pMap->Regions.push_back (region_t ());
		region_t & region = m_pMap->Regions.back ();
		region.pCountry = & country;

		const CCrdAdm & adm = pAdms [cAdm];
		assert (adm.dwOrdinal == cAdm + 1);

		ReadString (region.strName, adm.Name);

		mapRegions [adm.dwOrdinal] = & region;

		// Parse subadministrations.
		const CCrdSubadm * const pSubadms = reinterpret_cast<const CCrdSubadm *> (_pData + adm.dwSubadmOffset);
		for (size_t cSubadm = 0; cSubadm < adm.wSubadmNum; ++ cSubadm) {
			m_pMap->Cities.push_back (city_t ());
			city_t & city = m_pMap->Cities.back ();
			cCity ++;

			const CCrdSubadm & subadm = pSubadms [cSubadm];
			assert (subadm.dwOrdinal == cCity + pHdr->wAdmNum);

			ReadString (city.strName, subadm.Name);
			city.pRegion = & region;

			mapCities [subadm.dwOrdinal] = & city;
		}
	}

	//
	// Parse the search index data.
	//
	const CCrdSearchItem * const pSearch = reinterpret_cast<const CCrdSearchItem *> (_pData + pHdr->dwSubadmDataOffset + pHdr->wSubadmNum*sizeof (CCrdSubadm));
//# if 0
# ifdef _DEBUG
	size_t cSearchNum = 0;
	size_t cType;
	for (cType = 0; cType < c_cCrdTypes; ++ cType) {
		const CCrdSearchItem & si = pSearch [cType];
		assert (si.dwIndex == 0);
//		assert (si.wAdmId == 0 || si.wAdmId == 1);
		cSearchNum += si.wNum;
	}
	assert (cSearchNum == pHdr->dwIndexSize);

	assert (pHdr->dwIndexOffset == pHdr->dwSubadmDataOffset + pHdr->wSubadmNum*sizeof (CCrdSubadm) + c_cCrdTypes*sizeof (CCrdSearchItem));
	const char * const pIndexStart = _pData + pHdr->dwIndexOffset;
	const char * const pIndexEnd = pIndexStart + pHdr->dwIndexSize*sizeof (CCrdIndexItem) + c_cCrdTypes*sizeof (DWORD);

	cType = 0;
	int iIndex = 0;
	const char * pIndex = pIndexStart;
	while (pIndex < pIndexEnd) {
		// Prepare for next type.
		int iLastPrev = -1;
		int iLastNext = -1;
		string_t strPrevFirstLetter;
		string_t strPrevLabel;
		bool bFirstLetterReached = true;
		bool bLastLetterReached = false;
		int iLastCur = iIndex;
		const int iFirstIndex = iIndex;

		const size_t cItems = * reinterpret_cast<const DWORD *> (pIndex);
		pIndex += sizeof (DWORD);

		assert (cType < c_cCrdTypes);
		assert (pSearch [cType].wNum == cItems);

		const CCrdIndexItem * const pItems = reinterpret_cast<const CCrdIndexItem *> (pIndex);
		for (size_t cItem = 0; cItem < cItems; ++ cItem) {
			const CCrdIndexItem & ii = pItems [cItem];

//			assert (ii.dwAdmId != 0);

			string_t strLabel;
			if (ii.dwNameIdx >= pHdr->dwIndexOffset && ii.dwNameIdx < _cFileSize && ii.dwNameLen)
				strLabel.assign (_pData + ii.dwNameIdx, ii.dwNameLen);

			const int iPrev = iIndex - ii.shBackward;
			if (ii.shBackward >= 0)
				assert (iPrev < (int) pHdr->dwIndexSize);

			const int iNext = iIndex + ii.shForward;
			if (iNext >= 0)
				assert ((size_t) iNext < pHdr->dwIndexSize);

			string_t strFirstLetter;
			if (! strLabel.empty ()) {
				const char * const pFirstLetter = strLabel.c_str ();
				const char * const pSecondLetter = ::CharNext (pFirstLetter);
				strFirstLetter.assign (pFirstLetter, pSecondLetter);
			}

			if (! strPrevFirstLetter.empty () && strFirstLetter == strPrevFirstLetter) {
				if (ii.shBackward >= 0)
					assert (iLastPrev == iPrev);

				if (ii.shForward > 0)
					assert (iLastNext == iNext);

				assert (
					::strcmp  (strPrevLabel.c_str (), strLabel.c_str ()) <= 0 ||
					::strcmpi (strPrevLabel.c_str (), strLabel.c_str ()) <= 0
				);
			} else {
				if (bFirstLetterReached)
					assert (ii.shBackward <= 0 || iPrev < iFirstIndex);
				else {
					assert (iPrev  == iLastCur);
					assert (iIndex == iLastNext);
				}

				if (ii.shForward != 0) {
					assert (bLastLetterReached == false);
					assert (iLastNext != iNext);
				} else
					bLastLetterReached = true;

				if (! strPrevFirstLetter.empty () || ! strFirstLetter.empty ())
					assert (::strcmp (strPrevFirstLetter.c_str (), strFirstLetter.c_str ()) <= 0);
				strPrevFirstLetter.swap (strFirstLetter);

				iLastCur = iIndex;
				if (! strPrevFirstLetter.empty ())
					bFirstLetterReached = false;
			}
			iLastPrev = iPrev;
			iLastNext = iNext;
			strPrevLabel.swap (strLabel);

			++ iIndex;
		}
		pIndex += cItems*sizeof (CCrdIndexItem);

		++ cType;
	}
	assert (pIndex == pIndexEnd);
# endif // _DEBUG

	//
	// Parse the map area header.
	//
	const char * const pMapHdr_ = _pData + pHdr->dwIndexOffset + pHdr->dwIndexSize*sizeof (CCrdIndexItem) + c_cCrdTypes*sizeof (DWORD);
	const CCrdMapHdr * const pMapHdr = reinterpret_cast<const CCrdMapHdr *> (pMapHdr_);

	// Name.
	m_pMap->Header.strName = pMapHdr->strName;

	// Id.
	char buf_id [20];
	::sprintf (buf_id, "%d", pMapHdr->dwMapId);
	m_pMap->Header.strID = buf_id;

	// Datum.
	if (pMapHdr->btDatum != 0)
		ReportWarning ("Some unsupported datum (#%d) other than WGS 84 is used. This may cause some coordinate shift.", pMapHdr->btDatum);

	assert (pMapHdr->lX0 < pMapHdr->lX1);
	assert (pMapHdr->lY0 < pMapHdr->lY1);

	const CCrdScale * const pScales = reinterpret_cast<const CCrdScale *> (pMapHdr_ + pMapHdr->dwScalesOffset);
	const CCrdBlock * const pBlocks = reinterpret_cast<const CCrdBlock *> (pMapHdr_ + pMapHdr->dwBlocksOffset);
	const CCrdType  * const pTypes  = reinterpret_cast<const CCrdType  *> (pMapHdr_ + pMapHdr->dwTypesOffset);

	const CCrdPolyBase * const pPolylines = reinterpret_cast<const CCrdPolyline *> (pMapHdr_ + pMapHdr->dwPolylinesOffset);
	const CCrdPolyBase * const pPolygons  = reinterpret_cast<const CCrdPolygon  *> (pMapHdr_ + pMapHdr->dwPolygonsOffset);
	const CCrdPoint    * const pPoints    = reinterpret_cast<const CCrdPoint    *> (pMapHdr_ + pMapHdr->dwPointsOffset);

	const CCrdPt * const pPts = reinterpret_cast<const CCrdPt *> (pMapHdr_ + pMapHdr->dwPtsOffset);

	m_pMap->Levels.resize (pMapHdr->btScaleNum + 1);
	m_pMap->Levels [pMapHdr->btScaleNum].cZoom = pMapHdr->btScaleNum;
	m_pMap->Levels [pMapHdr->btScaleNum].SetBasicScale (24 - 3*pMapHdr->btScaleNum);
	for (size_t cScale = 0; cScale < pMapHdr->btScaleNum; ++ cScale) {
		m_pMap->Levels [cScale].cZoom = cScale;
		m_pMap->Levels [cScale].SetBasicScale (24 - 3*cScale);

		const CCrdScale & scale = pScales [cScale];

# ifdef _DEBUG
		ReportText ("* Zoom level #%d: %d=%dx%d blocks, each %dx%d size.", cScale, scale.dwBlocks, scale.dwCols, scale.dwRows, scale.dwBlockWidth, scale.dwBlockHeight);
# endif // _DEBUG

		for (size_t cBlock = 0; cBlock < scale.dwBlocks; ++ cBlock) {
			const CCrdBlock & block = pBlocks [scale.dwFirstBlockIdx + cBlock];

			for (size_t cType = 0; cType < block.dwTypes; ++ cType) {
				const CCrdType & type = pTypes [block.dwFirstTypeIdx + cType];

				for (size_t cElem = 0; cElem < type.dwElems; ++ cElem) {
					RGN_KIND rk;
					USHORT ushType = 0;
					Holux2Garmin (type.dwElemType, rk, ushType);
					ushType = type.dwElemType;
					if (rk == rkPolygon || rk == rkPolyline) {
						const CCrdPolyBase & poly = (rk == rkPolygon ? pPolygons : pPolylines) [type.dwFirstElemIdx + cElem];

						// Label.
						string_t strLabel;
						ReadString (strLabel, poly.Label);

						// Points.
						assert (poly.lX0 <= poly.lX1);
						assert (poly.lY0 <= poly.lY1);

						points_t points (poly.dwPoints);
						for (size_t cPoint = 0; cPoint < poly.dwPoints; ++ cPoint) {
							const CCrdPt & pt = pPts [poly.dwFirstPointIdx + cPoint];
							points [cPoint].x =  (poly.lX0 + pt.wX)*c_fHoluxCell;
							points [cPoint].y = -(poly.lY0 + pt.wY)*c_fHoluxCell;
						}

						// City.
						const mapCities_t::const_iterator it = mapCities.find (poly.dwAdmId);
						city_t * pCity = it == mapCities.end () ? NULL : it->second;

						AddPoly (cScale, ushType, rk, points, strLabel, pCity, reinterpret_cast<const char *> (& poly) - _pData, sizeof (poly));
					} else {
						const CCrdPoint & point = pPoints [type.dwFirstElemIdx + cElem];

						// Label & Phone.
						string_t strLabelAndPhone;
						ReadString (strLabelAndPhone, point.Label);
						string_t strLabel, strPhone;
						if (! strLabelAndPhone.empty ()) {
							const char * const pColon = ::strrchr (strLabelAndPhone.c_str (), ':');
							if (pColon) {
								strLabel.assign (strLabelAndPhone.c_str (), pColon);
								strPhone.assign (pColon + 1);
							} else
								strLabel.swap (strLabelAndPhone);
						}

						// Position.
						const float fX =  point.lX*c_fHoluxCell;
						const float fY = -point.lY*c_fHoluxCell;

						// City.
						const mapCities_t::const_iterator it = mapCities.find (point.dwAdmId);
						city_t * pCity = it == mapCities.end () ? NULL : it->second;

						AddPoint (cScale, ushType, rk, fX, fY, strLabel, pCity, strPhone, reinterpret_cast<const char *> (& point) - _pData, sizeof (point));
					}
				}
			}
		}

		SetProgressStatus (50*(cScale + 1)/pMapHdr->btScaleNum);
	}

	m_pMap->file_refs.swap (m_file_refs);

	SetProgressStatus (50);

	return lsOK;
}
